# Copyright 2011 Al Cramer
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import pg
from defs import *
import lexer
from lexer import vcb
import serializer
from seqmap import SeqMap,scToStr,srSeqToStr
from parseRoles import RoleXfrm
from rematch import *
from xfrm import *
import vdom
import os
import sys

"""
This module defines transforms that establish syntax relations.
"""

# These transforms are performed at the end of parsing: they
# do not conform to the find/apply rule strategy used by "Xfrm",
# and are implemented  as simple functions.

def reduceSrClause(lst):
    """
    transform an "srClause": sequence of nodes, where all the nodes
    share the same scope and syntax-relation to that scope.
    """
    if len(lst) == 0:
        return
    # recurse thru child clauses
    for e in lst:
        for srClause in e.rel:
            reduceSrClause(srClause)
    # merge sequences of prep's
    prepMask = WP_PREP|WP_QUALPREP|WP_CLPREP
    l1 = [lst[0]]
    for e in lst[1:]:
        last = l1[len(l1)-1]
        if last.checkSc(prepMask) and \
            e.checkSc(prepMask) and \
            e.isLeaf():
            last.wrds.extend(e.wrds)
            last.E = e.E
            pg.removeNode(e)
            continue
        l1.append(e)
    # rewrite l1 to "lst", merging word sequences
    del lst[:]
    i = 0
    while i < len(l1):
        e = l1[i]
        i += 1
        if e.checkSc(WP_PUNCT):
            lst.append(e)
            continue
        # "e" is a word. It starts a phrase (which may consist solely
        # of this word).
        S = e
        if S.checkSc(prepMask|WP_CONJ):
            # bind this to the word that follows (if there is a word)
            if i<len(l1) and not l1[i].checkSc(WP_PUNCT):
                l1[i].head.extend(S.wrds)
                pg.removeNode(S)
                S = l1[i]
                i += 1
        # "i" is at term that follows S. If S is a leaf, merge any
        # leaves that follow.
        if S.isLeaf():
            while i<len(l1):
                if l1[i].checkSc(WP_PUNCT) or not l1[i].isLeaf():
                    break
                S.wrds.extend(l1[i].wrds)
                pg.removeNode(l1[i])
                i += 1
        # add S to the lst
        lst.append(S)

def printseq(seq):
    for e in seq:
        e.printme(None,-1)
        
def inferSubjects():
    """
    In general syntax relations don't cross puctuation and conjunction
    ("and","or") boundaries. This analysis catches several
    common patterns:
    "They left today, running as fast as they could"
    "I saw Sally drinking rum and smoking reefer"
    """
    # get sequence of verbs + top scope nodes
    seq = []
    e = pg.eS
    while e is not None:
        if e.isVerb() or e.scope is None:
            seq.append(e)
        e = e.nxt
    # pg.printme(None,"debug")
    # printseq(seq)
    parseRe.declRe("%commaPhr","[_, _and|_or? _then?]")
    parseRe.declRe("%conjPhr","_and|_or|_but _then?")
    while len(seq) >= 2:
        if parseRe.match(seq,"SubVerb %commaPhr|%conjPhr Mod? VerbNoSub"):
            e = parseRe.matchResult[0][0]
            ex = parseRe.matchResult[3][0]
            ex.vIsoSub = e
            pg.reduceHead(parseRe.matchResult[1][0],ex)
            # e is given same scope, syntax role, and relations as
            # its peer.
            scope = e.scope
            ex.scope = scope
            ex.sr = e.sr
            if scope is not None:
                relIx = scope.getRel(e);
                if relIx != -1:
                     scope.rel[relIx].append(ex)
            exIx = seq.index(ex)
            seq = seq[exIx:]
            continue
        seq = seq[1:]

def finiParse():
    """
    Perform all final parse actions: called after the last
    transform.
    """
    # the domain of verb expressions has now been defined: set the
    # spans accordingly.
    pg.validateSpan()
    # infer relations that cross comma/conjunction boundaries
    inferSubjects()
    # conjoin words in the same sr context to from phrases
    reduceSrClause(pg.getRootNodes())


class ScSeqToSrXfrm(SeqMapXfrm):
    """
    This transform using the mapping (scSeq->srSeq) to set 
    syntax relations between nodes.
    """
    def __init__(self,_name=''):
        SeqMapXfrm.__init__(self,_name)
        self.srSeq = []
        
    def vToStr(self,i):
        return "srSeq: %s" % srSeqToStr(self.srSeq[i])
    
    def printme(self,fp):
        fp.write('Xfrm "%s"\n' % self.name)
        self.rules.printme(fp,scToStr,srSeqToStr)
        for i in range(len(self.srSeq)):
            fp.write("d. %s\n" % (i, self.vToStr(i)))

    def serializeValues(self,mode):
        if mode == 'w':
            serializer.encodeLstLst(self.srSeq,8)
        else:
            self.srSeq = serializer.decodeLstLst(8)

    def applyRule(self,e,rule):
        ndSeq,vix = rule
        srSeq = self.srSeq[vix]
        # For each ndSeq[i], the corresponding element srSeq[i] gives
        # syntax-relation and scope, encoded into an 8-bit value.
        for i in range(0,len(ndSeq)):
            e = ndSeq[i]
            # low 4 bits give the syntax-relation value;
	    # hi 4 bits gives offset to the verb
	    offset = 0xf & (srSeq[i] >> 4)
	    assert i+offset < len(ndSeq)
	    if offset != 0:
		e.setScope(ndSeq[i+offset],0xf & srSeq[i])
        # resume walk at the successor to the last node covered by
        # the rule. 
        return ndSeq[len(ndSeq)-1].nxt
    
class SrXfrm(Xfrm):
    """
    Establish syntax relations. Thematic roles are set
    later by "RoleXfrm".
    """
    def __init__(self,_name=''):
        Xfrm.__init__(self,_name)
        
    def canExtendComplex(self,e):
        """
        helper for findRule: can e extend a verb complex?
        """
        # Punctuation and conjunctions end the entangled region.
        return  e is  not None and \
            not e.checkSc(WP_PUNCT|WP_CONJ)

    def findRule(self,e):
        if e.checkSc(WP_PUNCT|WP_CONJ):
            return None
        sawVerb = False
        ex = e
        while ex is not None:
            if ex.isVerb():
                sawVerb = True
            if not self.canExtendComplex(ex.nxt):
                break
            ex = ex.nxt
        if sawVerb:
            return [e,ex]
        return None
    
    def applyRule(self,e,rule):
        # rule is [S,E] defining a sub-region of the graph.
        # This is a verb complex: a sequence of one or more
        # verb domains, preceeded by a set of prelude nodes.
        S,E = rule
        savePg = pg.resetSpan(S,E)
        # Q and sub relations have been set.
        # Any remaining non-verb, un-scoped terms are assigned an
        # object relation to the closest preceeding verb
        scope = None
        ex = S
        while True:
            if ex.isVerb():
                scope = ex
            elif ex.scope is None and \
                scope is not None:
                ex.setScope(scope,SR_theme)
            if ex == E:
                break
            ex = ex.nxt
        vdom.parse()
        pg.restoreSpan(savePg)
	return E.nxt

class SvToQXfrm(Xfrm):
    """
    Context dependent transform of subject-verb to qualified
    expression.
    """

    def inSubRole(self,e):
        """ Can "e" be in a subject role? """
        return e.sr == SR_agent or \
             e.sr == SR_exper or \
             e.sr == SR_topic

    def isAvgtObjTerm(self,e):
        """ is "e" an object term in an avgt expression? """
        if e.sr == SR_theme or e.sr == SR_auxTheme:
            if len(e.scope.rel[SR_theme]) > 0 and \
                len(e.scope.rel[SR_auxTheme]) > 0:
                # We're an object term in AGVT context: "I gave
                # the guy sitting there an apple".
                return True
        return False

        
    def findRule(self,e):
        """ Returns verb node to be transformed """
        if e.checkVProp(VP_Gerund):
            if self.inSubRole(e) or self.isAvgtObjTerm(e):
                # "the girl sitting there" in subject or avgt object role
                return e
        elif e.checkVProp(VP_Participle):
            if e.scope is not None:
                if self.inSubRole(e) or self.isAvgtObjTerm(e):
                    # "the strongest wind ever recorded" in subject or
                    # avgt object role
                    return e
        elif vcb.checkScProp(e.sc, WP_QUERY) and \
            self.inSubRole(e) and \
            len(e.scope.rel[SR_isQby]) == 0:
            # "who ate the cake".
            return e.scope
        return None

    def applyRule(self,e,rule):
        # "rule" is verb node to be transformed
        v = rule
        if len(v.rel[SR_agent]) > 0:
            v.resetRel(SR_agent,SR_isQby)
        elif len(v.rel[SR_exper]) > 0:
            v.resetRel(SR_exper,SR_isQby)
        elif len(v.rel[SR_topic]) > 0:
            v.resetRel(SR_topic,SR_isQby)
        return v.nxt

    def postXfrm(self):
        pg.validateRel()

class InvertQXfrm(Xfrm):
    """
    Invert Q expressions. Given "the girl you saw", [the girl] gets
    the (sr,scope) attributes of the verb, and the verb becomes a
    modifier of [the girl].
    """

    def findRule(self,e):
        """ Returns node to be transformed into a qualified node """
        if e.sr == SR_isQby:
            return e
        return None

    def applyRule(self,e,rule):
        # rule is the node to be qualified
        q = rule
        v = q.scope
        q.scope = v.scope
        q.sr = v.sr
        v.sr = SR_modifies
        v.scope = q
        return q.nxt
    
    def postXfrm(self):
        pg.validateRel()
        # This is the last xfrm in the sequence of xfrm's that
        # constitutes the parse: call the function that performs
        # final operations.
        finiParse()

